'''
Description: 程序主GUI模块
Author: HowsenFisher
Date: 2021-04-01 21:11:33
LastEditTime: 2021-04-12 16:15:42
LastEditors: liutq
Reference: 
'''
import sys,random
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from NetTools.WordSender import *
from NetTools.WordReceiver import *
from threadTool.thread import *
from threadTool.process import *
from multiprocessing import Process
from NetTools.DeviceListener import *
from NetTools.DeviceHolder import *
from Config import conf
from NetTools.FileReceiver import FileReceiver
from NetTools.FileRequestListener import FileRequestListener
from FileTools.FolderCompare import FolderCompare
from SyncLogs.LogDealer import LogDealer
from FileTools.FolderWatcher import FolderWatcher
from Media.Video.VideoCatcher import VideoCatcher
from Media.Video.VideoReceiver import VideoReceiver
from Media.Voice.VoiceReceiver import VoiceReceiver
from Media.Voice.VoiceRecorder import VoiceRecorder
from Desktop.DesktopClient import DesktopClient
from Desktop.DesktopServer import DesktopServer


class ChatGUI(QMainWindow):
    '''
    @description: 程序主GUI类
    @param  {*}
    @return {*}
    '''
    # 设备列表刷新信号
    freshListSignal = pyqtSignal()
    # 新消息信号
    newMessageSignal = pyqtSignal(str)
    # 新info信号
    newInfoSignal = pyqtSignal(str)
    voiceStopSignal = pyqtSignal()
    CongJiVideoSignal = pyqtSignal(str)
    def __init__(self):
        # 使用父类的构造函数
        super().__init__()
        ###### 一些信息 #######
        self.canAutoSync = False
        self.ManualSync = True
        self.FolderWatcherObj = None
        self.isCongJi = True
        self.voiceStop = False
        # 设备列表
        self.userList = []
        # 屏幕自适应组件列表
        self.fitObjList = []
        # 屏幕自适应组件尺寸信息列表
        self.fitObjInfoList = []
        # 当前选中的设备地址
        self.currentAddr=None
        # 定义信号的触发函数
        self.freshListSignal.connect(self.freshList)
        self.newMessageSignal.connect(self.freshChatFrame)
        self.newInfoSignal.connect(self.insertNewInfo)
        self.voiceStopSignal.connect(self.StopVoice)
        self.CongJiVideoSignal.connect(self.CongJiVideo)
        # 获取电脑分辨率信息
        self.getScreenInfo()
        
        # GUI初始化
        self.initUI()
        # 创建设备挂起线程（等待别人发现）
        self.createDeviceHoldThread(conf.ip)
        # 创建文字接收线程
        self.createWordReceiveThread(conf.ip, conf.word_receive_port)
        # 创建文件接收线程
        self.createFileReceiveThread(conf.ip,conf.file_port,conf.share_dir)
        # 创建文件请求监听线程
        self.createFileRequestListenThread()
        # 创建文件夹变动监听线程
        self.createFolderWatcherThread()
        # 创建视频接收器进程
        self.createVideoReceiverProcess()
        # 创建音频接收器线程
        self.createVoiceReceiverThread()
        # 
        self.createDesktopServerThread()
        # 创建自动同步定时器
        self.AutoSyncTimer = QTimer(self)
        self.AutoSyncTimer.timeout.connect(self.Sync)
######################################################################################################
# 界面区

    def getScreenInfo(self):
        # 获取桌面对象
        self.desktop = QApplication.desktop()
        # 获取显示器分辨率大小
        self.screenRect = self.desktop.screenGeometry()
        # 获取显示器高度
        self.WinHeight = self.screenRect.height()
        # 获取显示器宽度
        self.WinWidth = self.screenRect.width()

    def initUI(self):
        # 根据屏幕分辨率设置窗口大小
        self.setGeometry(0, 0, self.WinWidth, self.WinHeight)
        # 设置窗口标题
        self.setWindowTitle('聊天器')
        # 初始化扫描按钮
        self.initScanButton()
        # 初始化退出按钮
        self.initExitButton()
        # 初始化聊天列表标题
        self.initChatListTilte()
        # 初始化聊天列表
        self.initChatList()
        # 初始化聊天标题
        self.initChatTitle("聊天区")
        # 初始化聊天框
        self.initChatFrame()
        # 初始化发送框
        self.initInputFrame()
        # 初始化发送按钮
        self.initSendButton()
        # 初始化呼叫按钮
        self.initCallButton()
        # 初始化清空输入按钮
        self.initClearChatButton()
        # 初始化菜单栏
        self.initMenuBar()
        # 初始化日志标题
        self.initInfoTitle()
        # 初始化日志显示
        self.initInfoFrame()
        # 初始化日志清空按钮
        self.initClearInfoButton()
        # 初始化同步按钮
        self.initSyncButton()
        # 设置总体透明度
        self.setAllObjOpacity(0.9)
        # 显示GUI
        self.show()
    
    '''
    @description: 初始化菜单栏函数
    @param  {*}
    @return {*}
    '''
    # TODO：完善菜单栏
    def initMenuBar(self):
        #创建一个菜单栏
        menubar = self.menuBar()
        #添加菜单File
        fileMenu = menubar.addMenu('设置')
        desktopMenu = menubar.addMenu('桌面共享')
        desktopAct = QAction('开启桌面共享', self, checkable=True)
        desktopAct.triggered.connect(lambda:self.openDesktop())
        tenAct = QAction('10s', self, checkable=True)
        tenAct.setStatusTip('10s')
        tenAct.setChecked(False)
        ten2Act = QAction('20s', self, checkable=True)
        ten2Act.setStatusTip('20s')
        ten2Act.setChecked(False)
        ten3Act = QAction('30s', self, checkable=True)
        ten3Act.setStatusTip('30s')
        ten3Act.setChecked(False)
        ten0Act = QAction('不开启自动同步', self, checkable=True)
        ten0Act.setStatusTip('不开启自动同步')
        ten0Act.setChecked(True)
        tenAct.triggered.connect(lambda:self.dealCheck(0,10))
        ten2Act.triggered.connect(lambda:self.dealCheck(1,20))
        ten3Act.triggered.connect(lambda:self.dealCheck(2,30))
        ten0Act.triggered.connect(lambda:self.dealCheck(3,0))
        self.timeList = [tenAct,ten2Act,ten3Act,ten0Act]
        self.autoSyncTime = 0
        # 定义菜单项：退出
        SetMenu = QMenu('自动同步', self)
        # 将退出挂载到File之下
        fileMenu.addMenu(SetMenu)
        desktopMenu.addAction(desktopAct)
        SetMenu.addAction(ten0Act)
        SetMenu.addAction(tenAct)
        SetMenu.addAction(ten2Act)
        SetMenu.addAction(ten3Act)

    '''
    @description: 初始化扫描按钮函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initScanButton(self):
        # 扫描按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/50, 5/75+6/10+1/6, 1/8, 1/10]
        # 扫描按钮对象
        self.ScanButton = QPushButton('扫描', self)
        # 设置扫描按钮尺寸
        self.ScanButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 扫描按钮点击触发设备扫描线程
        self.ScanButton.clicked.connect(lambda: self.createScanDeviceThread(conf.ip, conf.mask))
        # 将扫描按钮有关信息添加到自适应的两个列表里
        self.fitObjList.append(self.ScanButton)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化退出按钮函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initExitButton(self):
        # 退出按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/50+1/8, 5/75+6/10+1/6, 1/8, 1/10]
        # 退出按钮对象
        self.ExitButton = QPushButton('退出', self)
        # 设置退出按钮尺寸
        self.ExitButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 退出按钮点击触发退出函数
        self.ExitButton.clicked.connect(lambda: self.Exit())
        # 将扫描按钮有关信息添加到自适应的两个列表里
        self.fitObjList.append(self.ExitButton)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化同步按钮函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initSyncButton(self):
        # 同步按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50+5/12, 5/75+6/10+1/6, 7/48, 1/10]
        # 同步按钮对象
        self.SyncButton = QPushButton('同步文件', self)
        # 设置同步按钮尺寸
        self.SyncButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 同步按钮点击触发同步函数
        self.SyncButton.clicked.connect(lambda: self.Sync())
        # 将同步按钮有关信息添加到自适应的两个列表里
        self.fitObjList.append(self.SyncButton)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化清除info按钮的函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initClearInfoButton(self):
        # 清理info按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50+5/12+7/48, 5/75+6/10+1/6, 7/48, 1/10]
        # 清理info按钮对象
        self.ClearInfoButton = QPushButton('清空日志', self)
        # 设置清理info按钮尺寸
        self.ClearInfoButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 清理info按钮点击触发清理info函数
        self.ClearInfoButton.clicked.connect(lambda: self.ClearInfo())
        # 将清理info按钮有关信息添加到自适应的两个列表里
        self.fitObjList.append(self.ClearInfoButton)
        self.fitObjInfoList.append(SizeInfo)

    '''
    @description: 初始化设备列表函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initChatList(self):
        # 设备列表的尺寸信息，这么写是为了自适应
        SizeInfo = [1/50, 1/15, 1/4, 8/10-1/30]
        # 创建一个QListWidget对象，用于列表显示
        self.listWidget = QListWidget(parent=self)
        # 设置列表的字体  
        self.listWidget.setFont(QFont("宋体",8,QFont.Bold))
        # 遍历设备信息
        for userinfo in self.userList:
            # 创建QListWidgetItem对象，即列表项
            item = QListWidgetItem()
            # 制作列表项（美化）
            widget = self.makeUserListItemWidget(userinfo)
            # 将列表项添加到列表
            self.listWidget.addItem(item) 
            # 为列表项设置widget（使用美化）
            self.listWidget.setItemWidget(item, widget)  
        # 列表尺寸设定
        self.listWidget.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 将列表有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.listWidget)
        self.fitObjInfoList.append(SizeInfo)
        # 列表点击触发，自动携带列表项index
        self.listWidget.clicked.connect(self.chatListOnClick)
    
    '''
    @description: 初始化设备列表标题
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initChatListTilte(self):
        # 设备列表标题的尺寸信息，这么写是为了自适应
        SizeInfo = [1/50,1/30,1/4,1/30]
        # 创建标签
        self.ChatListTilte = QLabel(parent=self)
        # 标签嵌入字体
        self.ChatListTilte.setText("设备列表")
        # 设置标签尺寸
        self.ChatListTilte.setGeometry(self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 标签的背景填充更改为True，否则无法显示背景
        self.ChatListTilte.setAutoFillBackground(True)
        # 创建调色板
        palette=QPalette()
        # 调色板设定颜色为红色
        palette.setColor(QPalette.Window,Qt.red)
        # #标签加载调色板颜色
        self.ChatListTilte.setPalette(palette)
        # #设置文本居中显示
        self.ChatListTilte.setAlignment(Qt.AlignCenter)
        # 将设备列表标题有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.ChatListTilte)
        self.fitObjInfoList.append(SizeInfo)

    '''
    @description: 初始化聊天框标题
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} his_username
    '''
    def initChatTitle(self,his_username):
        # 聊天框标题的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50,1/30,5/12,1/30]
        # 创建标签
        self.chatTitle = QLabel(parent=self)
        # 标签嵌入字体
        self.chatTitle.setText(his_username)
        # 设置标签尺寸
        self.chatTitle.setGeometry(self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 标签的背景填充更改为True，否则无法显示背景
        self.chatTitle.setAutoFillBackground(True)
        # 创建调色板
        palette=QPalette()
        # 调色板设定颜色为绿色
        palette.setColor(QPalette.Window,Qt.green)
        # 标签加载调色板
        self.chatTitle.setPalette(palette)
        # 设置文本居中显示
        self.chatTitle.setAlignment(Qt.AlignCenter)
        # 将聊天框标题有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.chatTitle)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化Info标题
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initInfoTitle(self):
        # Info标题的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50+5/12,1/30,7/24,1/30]
        # 创建标签
        self.InfoTitle = QLabel(parent=self)
        # 标签嵌入字体
        self.InfoTitle.setText("日志信息")
        # 设置标签尺寸
        self.InfoTitle.setGeometry(self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 标签的背景填充更改为True，否则无法显示背景
        self.InfoTitle.setAutoFillBackground(True)
        # 创建调色板
        palette=QPalette()
        # 调色板设定颜色为黄色
        palette.setColor(QPalette.Window,Qt.yellow)
        # 标签加载调色板
        self.InfoTitle.setPalette(palette)
        # 设置文本居中显示
        self.InfoTitle.setAlignment(Qt.AlignCenter)
        # 将Info标题有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.InfoTitle)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化info框函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initInfoFrame(self):
        # info框的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50+5/12,5/75,7/24,12/20+1/6]
        # 创建文本框
        self.InfoFrame = QTextEdit(parent=self)
        # 设置文本框尺寸
        self.InfoFrame.setGeometry(self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 将Info框有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.InfoFrame)
        self.fitObjInfoList.append(SizeInfo)
        # 设置文本框字体
        self.InfoFrame.setFont(QFont('Arial',8))

    '''
    @description: 初始化聊天框函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initChatFrame(self):
        # 聊天框的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50,5/75,5/12,12/20]
        # 创建文本框
        self.chatFrame = QTextEdit(parent=self)
        # 设置文本框尺寸
        self.chatFrame.setGeometry(self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 将聊天框有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.chatFrame)
        self.fitObjInfoList.append(SizeInfo)
        # 设置文本框字体
        self.chatFrame.setFont(QFont('Arial',16))

    '''
    @description: 初始化输入框
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initInputFrame(self):
        # 输入框的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50,5/75+6/10,5/12,1/6]
        # 创建文本框
        self.inputFrame = QTextEdit(parent=self)
        # 设置文本框尺寸
        self.inputFrame.setGeometry(self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 将输入框有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.inputFrame)
        self.fitObjInfoList.append(SizeInfo)

    '''
    @description: 初始化发送按钮函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initSendButton(self):
        # 发送按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50+5/36+5/36, 5/75+6/10+1/6, 5/36, 1/10]
        # 创建发送按钮
        self.SendButton = QPushButton('发送', self)
        # 发送按钮设置尺寸
        self.SendButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 发送按钮点击触发发送文字函数
        self.SendButton.clicked.connect(lambda: self.sendWord(self.currentAddr[0],self.currentAddr[1],self.inputFrame.toPlainText()))
        # 将发送按钮有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.SendButton)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化呼叫按钮
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initCallButton(self):
        # 呼叫按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50+5/36, 5/75+6/10+1/6, 5/36, 1/10]
        # 创建呼叫按钮
        self.CallButton = QPushButton('呼叫', self)
        # 呼叫按钮设置尺寸
        self.CallButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 呼叫按钮点击触发发送文字函数
        self.CallButton.clicked.connect(self.Call)
        # 将呼叫按钮有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.CallButton)
        self.fitObjInfoList.append(SizeInfo)
    
    '''
    @description: 初始化清理聊天按钮
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initClearChatButton(self):
        # 清理聊天按钮的尺寸信息，这么写是为了自适应
        SizeInfo = [1/4+1/50, 5/75+6/10+1/6, 5/36, 1/10]
        # 创建清理聊天按钮
        self.ClearChatButton = QPushButton('清空输入', self)
        # 清理聊天按钮设置尺寸
        self.ClearChatButton.setGeometry(
            self.WinWidth*SizeInfo[0], self.WinHeight*SizeInfo[1], self.WinWidth*SizeInfo[2], self.WinHeight*SizeInfo[3])
        # 清理聊天按钮点击触发发送文字函数
        self.ClearChatButton.clicked.connect(lambda: self.ClearChat())
        # 将清理聊天按钮有关尺寸信息添加到自适应的两个列表里
        self.fitObjList.append(self.ClearChatButton)
        self.fitObjInfoList.append(SizeInfo)

    '''
    @description: 制作列表项函数（美化）
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} userinfo：设备信息
    '''
    def makeUserListItemWidget(self,userinfo):
        # 读取属性用户名，头像id，地址
        this_username = userinfo['username']
        avatar_id = userinfo['avatarid']
        this_addr = userinfo['addr']
        # TODO:未读消息数以及最新消息功能尚未开发
        unreadCount=0
        latestMessage="xxxxxxxxx"
        # 总Widget
        thisWidget = QWidget()
        # 总体横向布局
        layout_main = QHBoxLayout()
        # 头像显示
        map_l = QLabel()
        #头像大小
        map_l.setFixedSize(80, 80) 
        # 设置头像图片
        maps = QPixmap("./images/avatars/%s.png"%userinfo['avatarid']).scaled(80,80)
        map_l.setPixmap(maps)
        # 右边的纵向布局
        layout_right = QVBoxLayout()
        # 右下的的横向布局
        layout_right_down = QHBoxLayout()
        # 未读消息标签
        unreadCountLabel = QLabel()
        # 最新未读消息标签
        latestMessageLabel = QLabel()
        # 地址标签
        this_addrLabel = QLabel()
        # 用户名标签
        this_usernameLabel = QLabel()
        # 设定这些标签的文字
        unreadCountLabel.setText(str(unreadCount))
        latestMessageLabel.setText(str(latestMessage))
        this_addrLabel.setText(str(this_addr))
        this_usernameLabel.setText(this_username)
        # 将这些标签添加到对应布局
        layout_right_down.addWidget(unreadCountLabel)
        layout_right_down.addWidget(latestMessageLabel)
        layout_right_down.addWidget(this_addrLabel)
        # 按照从左到右, 从上到下布局添加
        # 添加最左边的头像
        layout_main.addWidget(map_l)  
        # 右边的纵向布局添加用户名以及右下角横向布局
        layout_right.addWidget(this_usernameLabel)  
        layout_right.addLayout(layout_right_down)
        # 总布局添加右边的布局
        layout_main.addLayout(layout_right)
        # widget设定布局
        thisWidget.setLayout(layout_main)
        return thisWidget
    
    '''
    @description: 设置所有组件透明度函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} opacity：透明度
    '''
    def setAllObjOpacity(self,opacity):
        # 遍历所有组件，设置透明度
        for obj in self.fitObjList:
            self.setOpacity(opacity,obj)

##############################################################################################
# 线程区
    def createDesktopServerThread(self):
        self.myDesktopServer = DesktopServer(conf.ip,conf.desktop_port)
        self.myDesktopServer.run()

    def createVideoReceiverProcess(self):
        self.myVideoReceiver = thread(target=lambda:VideoReceiver(self,conf.ip,conf.video_port))
        self.myVideoReceiver.start()


    def createVoiceReceiverThread(self):
        self.myVoiceReceiver = thread(target=lambda:VoiceReceiver(self,conf.ip,conf.voice_port))
        self.myVoiceReceiver.start()

    '''
    @description: 创建设备挂起线程函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机ip
    '''
    def createDeviceHoldThread(self, ip):
        # 创建设备挂起线程
        self.DeviceHoldThread = thread(lambda: self.initDeviceHolder(ip))
        # 开启线程
        self.DeviceHoldThread.start()
    
    '''
    @description: 创建设备扫描线程函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机ip
    @param {*} mask：本机子网掩码
    '''
    def createScanDeviceThread(self,ip,mask):
        # 创建设备扫描线程
        self.DeviceScanThread = thread(lambda: self.scan(ip,mask))
        # 开启线程
        self.DeviceScanThread.start()

    '''
    @description: 创建文字接收线程函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机ip
    @param {*} port：文字接收端口
    '''
    def createWordReceiveThread(self, ip, port):
        # 创建文字接收线程
        self.WordReceiverThread = thread(
            lambda: self.initWordReceiver(ip, port))
        # 开启线程
        self.WordReceiverThread.start()
    
    '''
    @description: 创建文件接收线程函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机IP
    @param {*} port：文件接收端口
    @param {*} savePath：文件保存位置
    '''
    def createFileReceiveThread(self,ip,port,savePath):
        # 创建文件接收线程
        self.FileReceiverThread = thread(lambda:self.initFileReceiver(ip,port,savePath))
        # 开启线程
        self.FileReceiverThread.start()
    
    '''
    @description: 创建文件请求监听线程函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def createFileRequestListenThread(self):
        # 创建文件请求监听线程
        self.FileRequestListenThread = thread(lambda:self.initFileRequestListen())
        # 开启线程
        self.FileRequestListenThread.start()
    
    '''
    @description: 创建文件夹监视线程函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def createFolderWatcherThread(self):
        # 创建文件夹监视线程
        self.FolderWatcherThread = thread(lambda:self.initFolderWacher())
        # 开启线程
        self.FolderWatcherThread.start()

########################################################################################################
# 功能区
    def openDesktop(self):
        print("打开桌面")
        self.myDesktopClient = DesktopClient(self.currentAddr[0],conf.desktop_port)
        self.myDesktopClient.run()

    def CongJiVideo(self,ip):
        print("从机返回视频")
        a1 = thread(target=lambda:VoiceRecorder(self,ip,conf.voice_port))
        a1.start()
        time.sleep(1)
        b1 = thread(target=lambda:VideoCatcher(self,ip,conf.video_port))
        b1.start()
        time.sleep(1)

    def StopVoice(self):
        self.voiceStop = True
        print("Stop voice")

    def initVideoReceiver(self):
        myVideoReciever = VideoReceiver(self,conf.ip,conf.video_port)

    def Call(self):
        self.isCongJi = False
        self.voiceStop = False
        a = thread(target=lambda:VoiceRecorder(self,self.currentAddr[0],conf.voice_port))
        a.start()
        time.sleep(1)
        b = thread(target=lambda:VideoCatcher(self,self.currentAddr[0],conf.video_port))
        b.start()
        time.sleep(1)
        # a.wait()
        # b.wait()


    def dealCheck(self,state,time):
        for index,timeObj in enumerate(self.timeList):
            timeObj.setChecked(False)
        self.timeList[state].setChecked(True)
        if state == 3:
            self.PrintInfo("关闭自动同步")
            self.ManualSync = True
            self.canAutoSync =False
        else:
            self.ManualSync = False
            self.canAutoSync =True
            self.PrintInfo("每%ss自动同步"%str(time))
            self.autoSyncTime = time*1000
            self.autoSync()

    def autoSync(self):
        self.PrintInfo("开始自动同步，时间间隔%ds"%(int(self.autoSyncTime)/1000))
        self.AutoSyncTimer.start(self.autoSyncTime)

    '''
    @description: 在info框插入info函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} text：需要插入的文字
    '''
    def insertNewInfo(self,text):
        # 在info框打印
        self.PrintInfo(text)

    '''
    @description: 在info框打印函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} text：需要打印的文字
    '''
    def PrintInfo(self,text):
        # 将输入框光标移到最后
        self.InfoFrame.moveCursor(QTextCursor.End)
        # 在最后添加文字
        self.InfoFrame.append(str(text))
    
    '''
    @description: 清除聊天信息函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def ClearChat(self):
        # 清除文本框内容
        self.inputFrame.clear()
    
    '''
    @description: 清除info信息函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def ClearInfo(self):
        # 清除文本框内容
        self.InfoFrame.clear()

    '''
    @description: 刷新设备列表函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def freshList(self):
        # 清空设备列表
        self.listWidget.clear()
        # 遍历设别信息
        for userinfo in self.userList:
            # 创建QListWidgetItem对象，即列表项
            item = QListWidgetItem()  
            # 设置列表项初始大小
            item.setSizeHint(QSize(self.WinWidth/5,self.WinHeight/12))
            # 随机生成rgb三色
            r,g,b = tuple(random.choice(range(32,256,32)) for _ in range(3))
            # 设定随机背景颜色
            item.setBackground(QColor(r,g,b))
            # 制作美化的widget
            widget = self.makeUserListItemWidget(userinfo)
            # 将列表项添加到列表
            self.listWidget.addItem(item) 
            # 为列表项设置widget
            self.listWidget.setItemWidget(item, widget)  

    '''
    @description: 扫面子网设备函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机ip地址
    @param {*} mask：本机的子网掩码
    '''
    def scan(self, ip, mask):
        # 打印info
        self.PrintInfo("scan device in Net......")
        # 扫描按钮显示扫描中......
        self.ScanButton.setText("扫描中......")
        # 创建设别监听器
        myDeviceListener = DeviceListener(ip, mask)
        # 设备监听器开始扫描
        DeviceList = myDeviceListener.scan()
        # 扫描按钮显示扫描
        self.ScanButton.setText("扫描")
        # 将扫描到的信息转换为json数组
        infoList=[]
        for item in DeviceList:
            username = item[0]
            addr = item[1]
            avatar = item[2]
            infoList.append({'username':username,'addr':addr,'avatarid':avatar})
        # 更新设备列表
        self.userList = infoList
        # 递交刷新设备列表信号
        self.freshListSignal.emit()

    '''
    @description: 初始化设备挂起器函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机IP地址
    '''
    def initDeviceHolder(self, ip):
        # 打印info
        self.PrintInfo("初始化设别挂起器")
        # 创建设备挂起器
        self.myDeviceHolder = DeviceHolder(self,ip)
        # 设备挂起器开始挂起
        self.myDeviceHolder.hold()
    
    '''
    @description: 初始化文字接收器函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机IP地址
    @param {*} port：文字接收端口
    '''
    def initWordReceiver(self, ip, port):
        # 打印info
        self.PrintInfo("初始化文字接收器")
        # 创建文字接收器
        self.myWordReceiver = WordReceiver(self,ip, port)
    

    '''
    @description: 初始化文件接收器函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} ip：本机IP
    @param {*} port：文件接收端口
    @param {*} savePath：文件保存位置
    '''
    def initFileReceiver(self,ip,port,savePath):
        # 创建文件接收器
        self.myFileReceiver = FileReceiver(self,ip, port,savePath)
    
    '''
    @description: 初始化文件请求监听器函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initFileRequestListen(self):
        # 创建文件请求监听器
        self.myFileRequesterListener = FileRequestListener(self)
    
    '''
    @description: 初始化文件夹监听器
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def initFolderWacher(self):
        # 创建文件夹监听器
        self.myFolderWatcher = FolderWatcher(self,conf.share_dir)

    '''
    @description: 发送文字函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} addr：对方的IP
    @param {*} port：文字接收端口
    @param {*} word：发送的文字
    '''
    def sendWord(self, addr, port, word):
        # 打印info
        self.PrintInfo("send %s to %s,%s" % (word,addr, port))
        # 文字发送器发送文字，内容存在数据库
        self.myWordSender.sendWord(word)
        # 更新聊天框
        self.freshChatFrame()

    '''
    @description: 更新聊天框
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def freshChatFrame(self):
        # 聊天框清空
        self.chatFrame.clear()
        # 若仍未选择设备进行聊天
        if not self.currentAddr:
            return
        # 从数据库获取二人对话内容
        msgList = getMessageByHisIPandMySendTo(self.currentAddr[0])
        # 遍历按顺序显示
        for msg in msgList:
            # 如果Sender是自己，打印在右侧
            if msg['Sender']==conf.ip:
                # 设置右对齐
                self.chatFrame.setAlignment(Qt.AlignRight)
                # 设置字体颜色为blue
                self.chatFrame.setTextColor(QColor(0,0,255))
                # 聊天框插入文字
                self.chatFrame.insertPlainText("%s\n"%(msg['content']))
            # 否则，打印在右侧
            else:
                # 设置左对齐
                self.chatFrame.setAlignment(Qt.AlignLeft)
                # 设置字体颜色为red
                self.chatFrame.setTextColor(QColor(255,0,0))
                # 聊天框插入文字
                self.chatFrame.insertPlainText("%s\n"%(msg['content']))
    
    '''
    @description: 同步函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def Sync(self):
        self.SyncThread = thread(target=self.SyncThreadRun)
        # self.SyncThread.wait(2000)
        self.SyncThread.start()
    
    def SyncThreadRun(self):
        if not self.canAutoSync and self.ManualSync:
            return
        # 创建文件比较器，比较本地共享文件和数据库，生成log
        myCompare = FolderCompare(self,conf.share_dir)
        # 创建log处理器
        mylogdealer = LogDealer(self,conf.sync_log_dir)
        # self.FolderWatcherObj.stop()
        # log处理器开始处理，根据log内容执行动作
        mylogdealer.Deal()

    '''
    @description: GUI背景图片函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def fitBackground(self):
        # 创建调色板
        palette = QPalette()
        # 创建图片map对象
        pix = QPixmap("./images/1.jpg")
        # 设置图片尺寸
        pix = pix.scaled(self.width(),self.height())
        # 调色板强刷为图片
        palette.setBrush(QPalette.Background,QBrush(pix))
        # GUI设置调色板
        self.setPalette(palette)
    
    '''
    @description: 设置透明度函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} opacity：透明度
    @param {*} Obj：要设置透明度的组件
    '''
    def setOpacity(self,opacity,Obj):
        # 创建调色板
        pl = Obj.palette()
        # 调色板强刷为透明度为opacity的白色图片
        pl.setBrush(QPalette.Base,QBrush(QColor(255,255,255,255*opacity)))
        # 将组件的色彩设置为此调色板
        Obj.setPalette(pl)

    '''
    @description: 退出函数
    @param  {*}
    @return {*}
    @param {*} self
    '''
    def Exit(self):
        # 窗体关闭
        self.close()

##################################################################################################
# 事件处理区

    '''
    @description: 设备列表点击事件函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} event：事件对象
    '''
    def chatListOnClick(self, event):
        # 打印info
        self.PrintInfo("你选择了：%s"%self.userList[event.row()]['username'])
        # 获取选中的设备地址
        addr = self.userList[event.row()]['addr']
        # 打印info
        self.PrintInfo(addr)
        # 聊天标题设置为选中的用户名和地址
        self.chatTitle.setText(self.userList[event.row()]['username']+"  "+str(addr))
        # 将当前选中聊天设备的地址更新
        self.currentAddr=addr
        # 将当前选中聊天设备的用户名更新
        self.his_username = self.userList[event.row()]['username']
        # 打印info
        self.PrintInfo("初始化文字发送器")
        try:
            # 先将文字发送器关一遍，避免发生错误
            self.myWordSender.close()
        except Exception:
            pass
        # 创建文字发送器
        self.myWordSender = WordSender(addr[0], addr[1])
        # 从数据库获取聊天记录，刷新聊天列表
        self.freshChatFrame()

    '''
    @description: 窗体大小变化触发函数，用于所有组件自适应大小
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} event：事件对象
    '''
    def resizeEvent(self, event):
        # 获取变化后的宽高
        w = event.size().width()
        h = event.size().height()
        # 用新的宽高调整所有组件尺寸
        self.fitAllObj(w, h)
        # 用新的宽高调整背景图片
        self.fitBackground()

    '''
    @description: 调整所有组件尺寸（组件自适应）函数
    @param  {*}
    @return {*}
    @param {*} self
    @param {*} w：宽度
    @param {*} h：高度
    '''
    def fitAllObj(self, w, h):
        # 遍历自适应信息
        for Obj, info in zip(self.fitObjList, self.fitObjInfoList):
            # 重新设定原件尺寸
            Obj.setGeometry(w*info[0], h*info[1], w*info[2], h*info[3])


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = ChatGUI()
    sys.exit(app.exec_())
